package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult getPageResult(RequestParams requestParams) {
        //响应的数据
        SearchResponse response = null;

        //创建发送查询请求对象
        SearchRequest request = new SearchRequest("hotel");
        //品牌、城市、星级、价格等过滤功能功能实现
        filterQuery(request, requestParams);
//        //建立dsl语句
//        if (requestParams.getKey() == null || StringUtils.isEmpty(requestParams.getKey())) {
//            //如果前端传来的key为null或为‘’；查询es中所有的数据
//            request.source().query(QueryBuilders.matchAllQuery());
//        } else {
//            //根据key全局检索查询
//            request.source().query(QueryBuilders.matchQuery("all", requestParams.getKey()));
//        }
        //分页查询==根据起始索引分页
        //索引=（搜索页数-1）*每页条数

        //根据地理坐标查询
        //判断前端请求的经纬度是否为空
        if (requestParams.getLocation() != null &&
                !StringUtils.isEmpty(requestParams.getLocation())) {
            //构建dsl语句
            request.source().sort(
                    //以requestParams.getLocation()为圆心查找
                    SortBuilders.geoDistanceSort("location", new GeoPoint(requestParams.getLocation()))
                            //距离升序排行
                            .order(SortOrder.ASC)
                            //unit--查询附近多少千米距离的
                            //DistanceUnit.KILOMETERS为枚举类
                            .unit(DistanceUnit.KILOMETERS));
        }


        request.source().from((requestParams.getPage() - 1) * requestParams.getSize()).
                size(requestParams.getSize());
        //根据es客户端发送请请求
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //解析响应数据==获取响应hits的数据
        SearchHit[] hits = response.getHits().getHits();
        ArrayList<HotelDoc> hotelDocs = new ArrayList<>();
        //循环封装
        for (SearchHit hit : hits) {
            //获取文档中需要封装的对象
            String sourceAsString = hit.getSourceAsString();
            //将json格式发序列为java对象
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            //获取宾馆的距离
            Object[] sortValues = hit.getSortValues();
            if (sortValues.length > 0) {
                hotelDoc.setDistance(sortValues[0]);
            }
            //将返回的数据添加到集合中
            hotelDocs.add(hotelDoc);

        }
        //将数据封装到HotelDoc中
        //获取返回对象
        PageResult pageResult = new PageResult();
        //将总条数封装
        pageResult.setTotal(response.getHits().getTotalHits().value);
        //将查询到的数据封装
        pageResult.setHotels(hotelDocs);
        //返回所有的数据
        return pageResult;
    }


    private void filterQuery(SearchRequest request, RequestParams requestParams) {
        //构建布尔类查询对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //第一条件查询
        if (requestParams.getKey() == null || StringUtils.isEmpty(requestParams.getKey())) {
            //查询所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", requestParams.getKey()));
        }
        //根据城市条件查询
        if (requestParams.getCity() != null && !StringUtils.isEmpty(requestParams.getCity())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
        }
        //根据品牌条件查询
        if (requestParams.getBrand() != null && !StringUtils.isEmpty(requestParams.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
        }
        //根据星级条件查询
        if (requestParams.getStarName() != null && !StringUtils.isEmpty(requestParams.getStarName())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
        }
        //根据价格区间查询
        if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders
                    .rangeQuery("price")
                    .gte(requestParams.getMinPrice())
                    .lte(requestParams.getMaxPrice())
            );
        }

        //算分函数查询
        // 2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        // 原始查询，相关性算分的查询
                        boolQueryBuilder,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 其中的一个function score 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        // 过滤条件
                                        QueryBuilders.termQuery("isAD", true),
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        });

        request.source().query(boolQueryBuilder);


    }

    //实现
    @Override
    public Map<String, List<String>> getMap(RequestParams requestParams) throws IOException {
//{key: "da", page: 1, size: 5, sortBy: "default", starName: "四星", city: "上海", brand: "速8"}
        //获取查询请求对象
        SearchRequest request = new SearchRequest("hotel");
        //准备dsl语句
        //bool查詢
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //第一条件查询
        if (requestParams.getKey() == null || StringUtils.isEmpty(requestParams.getKey())) {
            //查询所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", requestParams.getKey()));
        }
        //根据城市条件查询
        if (requestParams.getCity() != null && !StringUtils.isEmpty(requestParams.getCity())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
        }
        //根据品牌条件查询
        if (requestParams.getBrand() != null && !StringUtils.isEmpty(requestParams.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
        }
        //根据星级条件查询
        if (requestParams.getStarName() != null && !StringUtils.isEmpty(requestParams.getStarName())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
        }
        //{key: "", page: 1, size: 5, sortBy: "default", minPrice: 300, maxPrice: 600}
        //根据价格大小查询
        if (requestParams.getMaxPrice() != null && requestParams.getMaxPrice() > 0) {
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price")
                    .gte(requestParams.getMinPrice())
                    .lte(requestParams.getMaxPrice()));
        }


        request.source().query(boolQueryBuilder);
        //将这些词条设置展示为0
        request.source().size(0);
        //设置聚合函数
        //根据starNamm賓館星級聚合
        request.source().aggregation(AggregationBuilders
                .terms("star")
                .field("starName")//根據什麽字段來聚合查詢
                .size(100));//設置聚合查詢的數量
        request.source().aggregation(AggregationBuilders
                .terms("brand")
                .field("brand")
                .size(100));
        request.source().aggregation(AggregationBuilders
                .terms("city")
                .field("city")
                .size(100));
        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //獲取返滬的所有數據
        Aggregations aggregations = response.getAggregations();
        //根據聚合名獲取
        Terms star = aggregations.get("star");
        Terms brand = aggregations.get("brand");
        Terms city = aggregations.get("city");
        //獲取其中的所有的桶數據
        List<? extends Terms.Bucket> star1 = star.getBuckets();
        List<? extends Terms.Bucket> brand1 = brand.getBuckets();
        List<? extends Terms.Bucket> city1 = city.getBuckets();
        ArrayList<String> list = new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();
        //便利集合得到所有的數據
        for (Terms.Bucket bucket : star1) {
            String keyAsString = bucket.getKeyAsString();
            list.add(keyAsString);
        }
        map.put("star", list);
        ArrayList<String> list1 = new ArrayList<>();
        for (Terms.Bucket bucket : brand1) {
            String keyAsString = bucket.getKeyAsString();
            list1.add(keyAsString);
        }
        map.put("brand", list1);
        ArrayList<String> list2 = new ArrayList<>();
        for (Terms.Bucket bucket : city1) {
            String keyAsString = bucket.getKeyAsString();
            list2.add(keyAsString);
        }
        map.put("city", list2);
        System.out.println("list2 = " + list2);
        return map;
    }

    //实现分词查询(以拼音缩写查询
    @SneakyThrows
    @Override
    public List<String> fenCi(String key) {
        //构建查询对象
        SearchRequest request = new SearchRequest("hotel");
        //准备dsl语句
        request.source().suggest(
                new SuggestBuilder().addSuggestion(
                        "suggestions",//自定义名字
                        SuggestBuilders
                                .completionSuggestion("suggestion")//自动补全的字段
                                .prefix(key)//前缀(即以key做分词的
                                .skipDuplicates(true)//去重
                                .size(10)//搜索的条数
                )
        );
        //发送请求
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
        //获取结果集
        Suggest suggest = search.getSuggest();
        //根据上面的自定义名字，拿到分词后的结果集
      CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        //获取options
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        List<String> list = new ArrayList<>();
        //遍历集合
        for (CompletionSuggestion.Entry.Option option : options) {
            //获取分词后的实际字段
            Text text = option.getText();
            list.add(text.toString());
        }
        return list;
    }


}
